﻿using System;
using System.Collections.Generic;
using System.Text;
using RaisingStudio.Data.Common.Converters;
using System.Xml;
using RaisingStudio.Common.Reflection;
using RaisingStudio.Common.Xml;

namespace RaisingStudio.Data.Common.Managers
{
    public class ConverterManager
    {
        #region Default Instance
        private static volatile ConverterManager _default = null;
        public static ConverterManager Default
        {
            get
            {
                if (_default == null)
                {
                    lock (typeof(ConverterManager))
                    {
                        if (_default == null)
                        {
                            _default = new ConverterManager();
                            _default.LoadConverters();
                        }
                    }
                }
                return _default;
            }
        }
        #endregion

        public static object ConvertDataValue(System.Data.DbType dbType, Type type, object value)
        {
            DataConverter converter = GetDataConverter(dbType, type);
            if (converter != null)
            {
                return converter(value);
            }
            return value;
        }

        public static object ConvertDataValue(Type type, System.Data.DbType dbType, object value)
        {
            DataConverter converter = GetDataConverter(type, dbType);
            if (converter != null)
            {
                return converter(value);
            }
            return value;
        }

        public static DataConverter GetDataConverter(System.Data.DbType dbType, Type type)
        {
            return GetDataConverter(ConverterManager.Default, dbType, type, false);
        }

        public static DataConverter GetDataConverter(ConverterManager converterManager, System.Data.DbType dbType, Type type, bool convertSystemType)
        {
            if (dbType == System.Data.DbType.Int32)
            {
                #region Int32
                if ((type == typeof(bool)) || (type == typeof(bool?)))
                {
                    return new DataConverter(ConvertInt32ToBool);
                }
                else if (type.IsEnum)
                {
                    return new DataConverter(new Int32ToEnumConverter(type).Convert);
                }
                else if (type.IsGenericType)
                {
                    if (type.GetGenericTypeDefinition() == typeof(Nullable<>))
                    {
                        Type argumentsType = type.GetGenericArguments()[0];
                        if (argumentsType.IsEnum)
                        {
                            return new DataConverter(new Int32ToEnumConverter(argumentsType).Convert);
                        }
                        else if ((convertSystemType) || (!TypeManager.IsWellKnownDataType(argumentsType)))
                        {
                            return converterManager.GetFromDbTypeDataConverter(dbType, argumentsType);
                        }
                    }
                }
                #endregion
            }
            else if (dbType == System.Data.DbType.String)
            {
                #region String
                if (type.IsEnum)
                {
                    return new DataConverter(new EnumConverter(type).ConvertFromDbType);
                }
                else if (type.IsGenericType)
                {
                    if (type.GetGenericTypeDefinition() == typeof(Nullable<>))
                    {
                        Type argumentsType = type.GetGenericArguments()[0];
                        if (argumentsType.IsEnum)
                        {
                            return new DataConverter(new EnumConverter(argumentsType).ConvertFromDbType);
                        }
                        else if ((convertSystemType) || (!TypeManager.IsWellKnownDataType(argumentsType)))
                        {
                            return converterManager.GetFromDbTypeDataConverter(dbType, argumentsType);
                        }
                    }
                }
                #endregion
            }

            // WellKnownDataType
            if ((convertSystemType) || (!TypeManager.IsWellKnownDataType(type)))
            {
                return converterManager.GetFromDbTypeDataConverter(dbType, type);
            }

            return null;
        }

        public static DataConverter GetDataConverter(Type type, System.Data.DbType dbType)
        {
            return GetDataConverter(ConverterManager.Default, type, dbType, false);
        }

        public static DataConverter GetDataConverter(ConverterManager converterManager, Type type, System.Data.DbType dbType, bool convertSystemType)
        {
            if (dbType == System.Data.DbType.Int32)
            {
                #region Int32
                if ((type == typeof(bool)) || (type == typeof(bool?)))
                {
                    return new DataConverter(ConvertBoolToInt32);
                }
                else if (type.IsEnum)
                {
                    return new DataConverter(ConvertEnumToInt32);
                }
                else if (type.IsGenericType)
                {
                    if (type.GetGenericTypeDefinition() == typeof(Nullable<>))
                    {
                        Type argumentsType = type.GetGenericArguments()[0];
                        if (argumentsType.IsEnum)
                        {
                            return new DataConverter(ConvertEnumToInt32);
                        }
                        else if ((convertSystemType) || (!TypeManager.IsWellKnownDataType(argumentsType)))
                        {
                            return converterManager.GetToDbTypeDataConverter(dbType, argumentsType);
                        }
                    }
                }
                #endregion
            }
            else if (dbType == System.Data.DbType.String)
            {
                #region String
                if (type.IsEnum)
                {
                    return new DataConverter(new EnumConverter(type).ConvertToDbType);
                }
                else if (type.IsGenericType)
                {
                    if (type.GetGenericTypeDefinition() == typeof(Nullable<>))
                    {
                        Type argumentsType = type.GetGenericArguments()[0];
                        if (argumentsType.IsEnum)
                        {
                            return new DataConverter(new EnumConverter(argumentsType).ConvertToDbType);
                        }
                        else if ((convertSystemType) || (!TypeManager.IsWellKnownDataType(argumentsType)))
                        {
                            return converterManager.GetToDbTypeDataConverter(dbType, argumentsType);
                        }
                    }
                }
                #endregion
            }

            // WellKnownDataType
            if ((convertSystemType) || (!TypeManager.IsWellKnownDataType(type)))
            {
                return converterManager.GetToDbTypeDataConverter(dbType, type);
            }

            return null;
        }

        public static object ConvertBoolToInt32(object value)
        {
            return System.Convert.ToInt32(value);
        }

        public static object ConvertInt32ToBool(object value)
        {
            return System.Convert.ToBoolean(value);
        }

        public static object ConvertEnumToInt32(object value)
        {
            return (int)value;
        }

        private const string CONVERTERS_FILE_NAME = "converters.config";
        private const string XML_CONVERTERS = "convertersConfig/converters/converter";

        private Dictionary<System.Data.DbType, Dictionary<System.Type, DataConverter>> _fromDbTypeDataConverters;
        private Dictionary<System.Data.DbType, Dictionary<System.Type, DataConverter>> _toDbTypeDataConverters;
        
        public ConverterManager()
        {
            this._fromDbTypeDataConverters = new Dictionary<System.Data.DbType, Dictionary<System.Type, DataConverter>>();
            this._toDbTypeDataConverters = new Dictionary<System.Data.DbType, Dictionary<System.Type, DataConverter>>();
        }

        private List<RaisingStudio.Data.Common.Configuration.Converter> converters;

        public void LoadConverters()
        {
            // TODO: read the config file and register the converters.
            XmlDocument xmlProviders = Resources.GetConfigAsXmlDocument(CONVERTERS_FILE_NAME);
            if ((xmlProviders != null) && (xmlProviders.DocumentElement != null))
            {
                this.converters = new List<RaisingStudio.Data.Common.Configuration.Converter>();
                AddConverters(xmlProviders);
            }
        }

        private object converters_lock = new object();

        public void AppendConverters(string baseDirectory)
        {
            XmlDocument xmlProviders = Resources.GetConfigAsXmlDocument(baseDirectory, CONVERTERS_FILE_NAME);
            if ((xmlProviders != null) && (xmlProviders.DocumentElement != null))
            {
                lock (this.converters_lock)
                {
                    AddConverters(xmlProviders);
                }
            }
        }


        private void AddConverters(XmlDocument xmlProviders)
        {
            RaisingStudio.Data.Common.Configuration.Converter converter;
            foreach (XmlNode node in xmlProviders.SelectNodes(XML_CONVERTERS))
            {
                converter = ConverterDeSerializer.Deserialize(node);
                // TODO: register converter.
                RegisterConverter(converter);
                this.converters.Add(converter);
            }
        }

        private void RegisterConverter(RaisingStudio.Data.Common.Configuration.Converter converter)
        {
            System.Data.DbType dbType = TypeManager.GetWellKnownDbType(converter.DbType);
            System.Type type = TypeManager.ResolveType(converter.Type);
            if (type != null)
            {
                System.Type converterType = TypeManager.ResolveType(converter.ConverterType);
                if (converterType != null)
                {
                    IDbTypeConverter dbTypeConverter = System.Activator.CreateInstance(converterType) as IDbTypeConverter;
                    if (converter != null)
                    {
                        RegisterConverter(dbType, type, dbTypeConverter);
                    }
                }
            }
        }

        public void RegisterConverter(System.Data.DbType dbType, System.Type type, IDbTypeConverter converter)
        {
            if (!this._fromDbTypeDataConverters.ContainsKey(dbType))
            {
                this._fromDbTypeDataConverters.Add(dbType, new Dictionary<System.Type, DataConverter>());
            }
            Dictionary<System.Type, DataConverter> fromDataConverters = this._fromDbTypeDataConverters[dbType];
            fromDataConverters[type] = new DataConverter(converter.ConvertFromDbType);

            if (!this._toDbTypeDataConverters.ContainsKey(dbType))
            {
                this._toDbTypeDataConverters.Add(dbType, new Dictionary<System.Type, DataConverter>());
            }
            Dictionary<System.Type, DataConverter> toDataConverters = this._toDbTypeDataConverters[dbType];
            toDataConverters[type] = new DataConverter(converter.ConvertToDbType);
        }

        public DataConverter GetFromDbTypeDataConverter(System.Data.DbType dbType, System.Type type)
        {
            if (this._fromDbTypeDataConverters.ContainsKey(dbType))
            {
                Dictionary<System.Type, DataConverter> fromDataConverters = this._fromDbTypeDataConverters[dbType];
                if (fromDataConverters.ContainsKey(type))
                {
                    return fromDataConverters[type];
                }
            }
            return null;
        }

        public DataConverter GetToDbTypeDataConverter(System.Data.DbType dbType, System.Type type)
        {
            if (this._toDbTypeDataConverters.ContainsKey(dbType))
            {
                Dictionary<System.Type, DataConverter> toDataConverters = this._toDbTypeDataConverters[dbType];
                if (toDataConverters.ContainsKey(type))
                {
                    return toDataConverters[type];
                }
            }
            return null;
        }

        private IDbTypeConverter _colorConverter;
        private IDbTypeConverter _pointConverter;
        private IDbTypeConverter _rectangleConverter;
        private IDbTypeConverter _sizeConverter;

        public void RegisterBasicConverters()
        {
#if (PocketPC || Smartphone || WindowsCE)
#else
            this._colorConverter = new RaisingStudio.Data.Common.Converters.ColorConverter();
            RegisterConverter(System.Data.DbType.String, typeof(System.Drawing.Color), this._colorConverter);
            
            this._pointConverter = new RaisingStudio.Data.Common.Converters.PointConverter();
            RegisterConverter(System.Data.DbType.String, typeof(System.Drawing.Point), this._pointConverter);

            this._rectangleConverter = new RaisingStudio.Data.Common.Converters.RectangleConverter();
            RegisterConverter(System.Data.DbType.String, typeof(System.Drawing.Rectangle), this._rectangleConverter);

            this._sizeConverter = new RaisingStudio.Data.Common.Converters.SizeConverter();
            RegisterConverter(System.Data.DbType.String, typeof(System.Drawing.Size), this._sizeConverter);
#endif
        }

        private IDbTypeConverter _stringBooleanConverter;
        private IDbTypeConverter _stringByteConverter;
        private IDbTypeConverter _stringDateTimeConverter;
        private IDbTypeConverter _stringDecimalConverter;
        private IDbTypeConverter _stringDoubleConverter;
        private IDbTypeConverter _stringGuidConverter;
        private IDbTypeConverter _stringInt16Converter;
        private IDbTypeConverter _stringInt32Converter;
        private IDbTypeConverter _stringInt64Converter;
        private IDbTypeConverter _stringSingleConverter;

        public void RegisterSystemConverters()
        {
            this._stringBooleanConverter = new RaisingStudio.Data.Common.Converters.StringBooleanConverter();
            RegisterConverter(System.Data.DbType.String, typeof(bool), this._stringBooleanConverter);

            this._stringByteConverter = new RaisingStudio.Data.Common.Converters.StringByteConverter();
            RegisterConverter(System.Data.DbType.String, typeof(byte), this._stringByteConverter);

            this._stringDateTimeConverter = new RaisingStudio.Data.Common.Converters.StringDateTimeConverter();
            RegisterConverter(System.Data.DbType.String, typeof(DateTime), this._stringDateTimeConverter);

            this._stringDecimalConverter = new RaisingStudio.Data.Common.Converters.StringDecimalConverter();
            RegisterConverter(System.Data.DbType.String, typeof(decimal), this._stringDecimalConverter);

            this._stringDoubleConverter = new RaisingStudio.Data.Common.Converters.StringDoubleConverter();
            RegisterConverter(System.Data.DbType.String, typeof(double), this._stringDoubleConverter);

            this._stringGuidConverter = new RaisingStudio.Data.Common.Converters.StringGuidConverter();
            RegisterConverter(System.Data.DbType.String, typeof(Guid), this._stringGuidConverter);

            this._stringInt16Converter = new RaisingStudio.Data.Common.Converters.StringInt16Converter();
            RegisterConverter(System.Data.DbType.String, typeof(Int16), this._stringInt16Converter);

            this._stringInt32Converter = new RaisingStudio.Data.Common.Converters.StringInt32Converter();
            RegisterConverter(System.Data.DbType.String, typeof(int), this._stringInt32Converter);

            this._stringInt64Converter = new RaisingStudio.Data.Common.Converters.StringInt64Converter();
            RegisterConverter(System.Data.DbType.String, typeof(Int64), this._stringInt64Converter);

            this._stringSingleConverter = new RaisingStudio.Data.Common.Converters.StringSingleConverter();
            RegisterConverter(System.Data.DbType.String, typeof(float), this._stringSingleConverter);
        }
    }
}
